<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>11.1相同的前置元素和后置元素</title>
</head>
<body>
  <div id="app"></div>
<script>

const newVnode = {
  type: 'div',
  children: [
    {type: 'h1', children: 'h1-1', key: 1}, 
    {type: 'span', children: 'span-4', key: 4},
    {type: 'p', children: 'p', key: 2}, 
    {type: 'div', children: 'div-3', key: 3},
  ]
}

const oldVnode = {
  type: 'div',
  children: [
    {type: 'h1', children: 'h1', key: 1},
    {type: 'p', children: 'p', key: 2}, 
    {type: 'div', children: 'div', key: 3}, 
  ]
}


function createRenderer(options) {
  
  const {createElement, setElementText, insert, patchProps, unmount} = options

  function render(vnode, container) {
    console.log(vnode,'vnode')
    if(vnode) {
      patch(container._vnode, vnode, container)
    } else {
      if(container._vnode) {
        unmount(container._vnode)
      }
    }
    container._vnode = vnode
  }

  function patch(n1, n2, container, anchor) {
    if(n1 && n1.type !== n2.type) {
      unmount(n1)
      n1 = null
    }
    const type = typeof n2.type
    if(type === 'string') {
      if(!n1) {
        mountElement(n2, container, anchor)
      } else {
        patchElement(n1, n2)
      }
    } else if(type === 'Text') {
      //挂载文本节点
    }else if(type === 'Fragment') {
      //
    }
  }

  function mountElement(vnode, container, anchor) {
    const {type, props, children} = vnode
    const el = vnode.el = createElement(type)
    if(typeof children === 'string') {//挂载子节点
      setElementText(el, children)
    } else if(Array.isArray(children)) {
      children.forEach((c)=> {
        patch(null, c, el, anchor)
      })
    }
    if(props) { //挂载属性
      for(key in props) {
        patchProps(el, key, null, props[key])
      }
    }
    insert(el, container, anchor)
  }

  function patchElement(n1, n2) {
    const el = n2.el = n1.el
    const oldProps = n1.props || {}
    const newProps = n2.props || {}
     //1.更新属性
    for(const key in oldProps) {
      console.log(key,'oldProps')
      if(!(key in newProps)) {
        console.log(key,'key')
        patchProps(el, key, oldProps[key], null)
      }
    }
    for(const key in newProps) {
      if(newProps[key] !== oldProps[key]) {
        patchProps(el, key, oldProps[key], newProps[key])
      }
    }
    //2.更新子节点
    patchChildren(n1, n2, el)
  }

  function patchChildren(n1, n2,container) {
    const {children: newChildren} = n2
    const {children: oldChildren} = n1
    if(typeof newChildren === 'string') {
      if(Array.isArray(oldChildren)) {
        oldChildren.forEach(c=> unmount(c))
      }
      setElementText(container, newChildren)
    } else if(Array.isArray(newChildren)) {
      if(Array.isArray(oldChildren)) {
        patchKeyChildren(n1, n2,container)
      } else {
        setElementText(container, '')
        newChildren.forEach(c=> mountElement(c, container))
      }
      
    } else {
      if(Array.isArray(oldChildren)) {
        oldChildren.forEach(c=> unmount(c))
      } else if(typeof oldChildren === 'string'){
        setElementText(container, '')
      }
    } 
  }

  function patchKeyChildren(n1, n2, container) {
    const newChildren = n2.children
    const oldChildren = n1.children
    //处理相同的前置节点
    //索引j指向新旧俩组节点的开头
    let j = 0
    let oldVnode = oldChildren[j]
    let newVnode = newChildren[j]
    //while 循环向右遍历，直到遇到拥有不同的 key 值的节点为止
    while(oldVnode.key === newVnode.key) {
      patch(oldVnode, newVnode, container)
      j++
      oldVnode = oldChildren[j]
      newVnode = newChildren[j]
    }

    //更新相同的后置节点
    //索引 oldEnd 指向旧的一组节点的最后一个节点
    //索引 newEnd 指向新的一组节点的最后一个节点
    let oldEnd = oldChildren.length - 1
    let newEnd = newChildren.length - 1
    oldVnode = oldChildren[oldEnd]
    newVnode = newChildren[newEnd]
    //while 循环从后向前遍历，直到遇到拥有不同的 key 值的节点 为止
    while(oldVnode.key === newVnode.key) {
      patch(oldVnode, newVnode, container)
      oldEnd --
      newEnd --
      oldVnode = oldChildren[oldEnd]
      newVnode = newChildren[newEnd]
    }

    //预处理完毕后，如果满足如下条件，则说明 j --> newEnd 之间的节点作为新的节点插入
    if(j > oldEnd && j <= newEnd) {
      const anchorIndex = newEnd + 1
      //锚点元素
      const anchor = anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null
      while(j <= newEnd) {
        patch(null, newChildren[j++], container, anchor)
      }
    }else if(j > newEnd && j <= oldEnd) {
      // j -> oldEnd 之间的节点应该被卸载
      while(j <= oldEnd) {
      unmount(oldChildren[j++])
      }
    }
  }

  return {
    render
  }
}
const renderer = createRenderer(
  {
    //创建节点
    createElement(tag) {
      return document.createElement(tag)
    },
    //设置文本
    setElementText(el, text) {
      el.textContent = text
    },
    //创建文本节点
    createText(text) {
      el.createTextNode(text)
    },
    //插入节点
    insert(el, parent, anchor = null) {
      parent.insertBefore(el, anchor)
    },
    //挂载属性
    patchProps(el, key, prevValue, nextValue) {
      if(shouldSetAsProps(el, key)) {//DOM-property
        const {type} = typeof el[key]
        if(type === 'boolean' && nextValue === '') {
          el[key] = false
        }else {
          el[key] = nextValue
        }
      } else if(/^on/.test(key)) {//处理事件
        let invokers = el._vai || (el._vai = {})
        let invoker = invokers[key]
        const name = key.slice(2).toLowerCase()
        if(nextValue) {
          if(!invoker) {
            invoker = el._vai[key] = function(e) {
              if(Array.isArray(invoker.value)) {
                invoker.value.forEach(fn=> fn(e))
              } else {
                invoker.value(e)
              }
            }
            invoker.value = nextValue
            
            el.addEventListener(name, invoker)
          } else {
            invoker.value = nextValue
          } 
        } else if(invoker) {
          el.removeEventListener(name, invoker)
        }
      }else {
        el.setAttribute(key, nextValue)
      }
    },
    //卸载
    unmount(vnode) {
      const el = vnode.el
      const parent = el.parentNode
      if(parent) parent.removeChild(el)
    }

  }
)

renderer.render(oldVnode, document.getElementById('app'))
renderer.render(newVnode, document.getElementById('app'))
function shouldSetAsProps(el, key) {
  if(key === 'form' && el.tagName === 'INPUT') return //只读属性
  return key in el
}
</script>
</body>

</html>